Coverage Report

Created: 2021-08-28 18:14

D:\git\skunkworks\herald-for-cpp\herald\include\herald\ble\ble_concrete_database.h
Line
Count
Source (jump to first uncovered line)
1
//  Copyright 2020-2021 Herald Project Contributors
2
//  SPDX-License-Identifier: Apache-2.0
3
//
4
5
#ifndef HERALD_BLE_CONCRETE_DATABASE_H
6
#define HERALD_BLE_CONCRETE_DATABASE_H
7
8
#include "ble_database.h"
9
#include "ble_receiver.h"
10
#include "ble_sensor.h"
11
#include "ble_transmitter.h"
12
#include "ble_concrete.h"
13
#include "ble_protocols.h"
14
#include "bluetooth_state_manager.h"
15
#include "ble_device_delegate.h"
16
#include "filter/ble_advert_parser.h"
17
#include "../payload/payload_data_supplier.h"
18
#include "../context.h"
19
#include "../data/sensor_logger.h"
20
#include "ble_sensor_configuration.h"
21
#include "ble_coordinator.h"
22
#include "../datatype/bluetooth_state.h"
23
24
#include <memory>
25
#include <vector>
26
#include <array>
27
#include <algorithm>
28
// #include <optional>
29
30
namespace herald {
31
namespace ble {
32
33
using namespace herald::datatype;
34
using namespace herald::ble::filter;
35
using namespace herald::payload;
36
37
38
/// \brief Provides a callable that assists in ordering for most recently updated BLEDevice
39
struct last_updated_descending {
40
0
  bool operator()(const BLEDevice& a, const BLEDevice& b) {
41
0
    return a.timeIntervalSinceLastUpdate() > b.timeIntervalSinceLastUpdate(); // opposite order
42
0
  }
43
};
44
45
template <typename ContextT, std::size_t MaxDevicesCached = 10>
46
class ConcreteBLEDatabase : public BLEDatabase, public BLEDeviceDelegate /*, public std::enable_shared_from_this<ConcreteBLEDatabase<ContextT>>*/  {
47
public:
48
  static constexpr std::size_t MaxDevices = MaxDevicesCached;
49
50
  ConcreteBLEDatabase(ContextT& context)
51
  : ctx(context),
52
    delegates(),
53
    devices()
54
    HLOGGERINIT(context,"herald","ConcreteBLEDatabase")
55
11
  {
56
11
    ;
57
11
  }
58
59
  ConcreteBLEDatabase(const ConcreteBLEDatabase& from) = delete;
60
  ConcreteBLEDatabase(ConcreteBLEDatabase&& from) = delete;
61
62
11
  ~ConcreteBLEDatabase() = default;
63
64
  // BLE Database overrides
65
66
2
  void add(BLEDatabaseDelegate& delegate) override {
67
2
    delegates.emplace_back(delegate);
68
2
  }
69
70
  // Creation overrides
71
0
  BLEDevice& device(const BLEMacAddress& mac, const Data& advert/*, const RSSI& rssi*/) override {
72
0
    // Check by MAC first
73
0
    TargetIdentifier targetIdentifier(mac.underlyingData());
74
0
    auto results = matches([&targetIdentifier](const BLEDevice& d) {
75
0
      return d.identifier() == targetIdentifier;
76
0
    });
77
0
    if (results.size() != 0) {
78
0
      // HTDBG("DEVICE ALREADY KNOWN BY MAC");
79
0
      // Assume advert details are known already
80
0
      return results.front(); // TODO ensure we send back the latest, not just the first match
81
0
      // res->rssi(rssi);
82
0
      // return res;
83
0
    }
84
0
85
0
    // Now check by pseudo mac
86
0
    auto segments = BLEAdvertParser::extractSegments(advert,0);
87
0
    // HTDBG("segments:-");
88
0
    // HTDBG(std::to_string(segments.size()));
89
0
    auto manuData = BLEAdvertParser::extractManufacturerData(segments);
90
0
    auto heraldDataSegments = BLEAdvertParser::extractHeraldManufacturerData(manuData);
91
0
    // HTDBG("herald data segments:-");
92
0
    // HTDBG(std::to_string(heraldDataSegments.size()));
93
0
    // auto device = db->device(bleMacAddress); // For most devices this will suffice
94
0
95
0
    // TODO Check for public herald service in ADV_IND packet - shown if an Android device, wearable or beacon in zephyr
96
0
    // auto serviceData128 = BLEAdvertParser::extractServiceUUID128Data(segments);
97
0
    // bool hasHeraldService = false;
98
0
    // for (auto& service : serviceData128) {
99
0
    //   if (service.uuid == heraldUuidData) {
100
0
    //     hasHeraldService = true;
101
0
    //     HTDBG("FOUND DEVICE ADVERTISING HERALD SERVICE");
102
0
    //     device->operatingSystem(BLEDeviceOperatingSystem::android);
103
0
    //   }
104
0
    // }
105
0
    
106
0
107
0
    if (0 != heraldDataSegments.size()) {
108
0
      // HTDBG("Found Herald Android pseudo device address in advert");
109
0
      // Try to FIND by pseudo first
110
0
      BLEMacAddress pseudo(heraldDataSegments.front());
111
0
      auto samePseudo = matches([&pseudo](const BLEDevice& d) {
112
0
        return d.pseudoDeviceAddress() == pseudo;
113
0
      });
114
0
      if (0 != samePseudo.size()) {
115
0
        // HTDBG("FOUND EXISTING DEVICE BY PSEUDO");
116
0
        return samePseudo.front();
117
0
      }
118
0
      // HTDBG("CREATING NEW DEVICE BY MAC AND PSEUDO ONLY");
119
0
      // Now create new device with mac and pseudo
120
0
      auto& newDevice = device(mac,pseudo);
121
0
      assignAdvertData(newDevice,std::move(segments), manuData);
122
0
      // newDevice->rssi(rssi);
123
0
      return newDevice;
124
0
    }
125
0
126
0
    // HTDBG("CREATING NEW DEVICE BY MAC ONLY");
127
0
128
0
    // Now create a device just from a mac
129
0
    auto& newDevice = device(targetIdentifier);
130
0
    // HTDBG("Got new device");
131
0
    assignAdvertData(newDevice,std::move(segments), manuData);
132
0
    // newDevice->rssi(rssi);
133
0
    // HTDBG("Assigned advert data");
134
0
    return newDevice;
135
0
  }
136
137
0
  BLEDevice& device(const BLEMacAddress& mac, const BLEMacAddress& pseudo) override {
138
0
    auto samePseudo = matches([&pseudo](const BLEDevice& d) {
139
0
      return d.pseudoDeviceAddress() == pseudo;
140
0
    });
141
0
    if (0 == samePseudo.size()) {
142
0
      auto& ptr = device(TargetIdentifier(pseudo.underlyingData()));
143
0
      ptr.pseudoDeviceAddress(pseudo);
144
0
      return ptr;
145
0
    }
146
0
    // get most recent and clone, then attach
147
0
    auto comp = last_updated_descending();
148
0
    std::sort(samePseudo.begin(),samePseudo.end(), comp); // functional style
149
0
    BLEDevice& updatedDevice = samePseudo.front();
150
0
    // TODO support calling card
151
0
    // auto toShare = shareDataAcrossDevices(pseudo);
152
0
    // if (toShare.has_value()) {
153
0
    //   updatedDevice.payloadData(toShare);
154
0
    // }
155
0
    
156
0
    // Has pseudo address so must be android
157
0
    updatedDevice.operatingSystem(BLEDeviceOperatingSystem::android);
158
0
159
0
    // register new device discovery date
160
0
    updatedDevice.registerDiscovery(Date());
161
0
162
0
    // devices.push_back(updatedDevice);
163
0
    for (auto& delegate : delegates) {
164
0
      delegate.get().bleDatabaseDidCreate(updatedDevice); // may be new with a new service
165
0
    }
166
0
    return updatedDevice;
167
0
  }
168
169
0
  BLEDevice& device(const BLEMacAddress& mac) override {
170
0
    return device(TargetIdentifier(mac.underlyingData()));
171
0
  }
172
173
1
  BLEDevice& device(const PayloadData& payloadData) override {
174
1
    auto pti = TargetIdentifier(payloadData);
175
1
    auto results = matches([&pti](const BLEDevice& d) {
176
1
      return d.identifier() == pti;
177
1
      // auto payload = d.payloadData();
178
1
      // if (!payload.has_value()) {
179
1
      //   return false;
180
1
      // }
181
1
      // return (*payload)==payloadData;
182
1
    });
183
1
    if (results.size() != 0) {
184
0
      return results.front(); // TODO ensure we send back the latest, not just the first match
185
0
    }
186
1
    BLEDevice& newDevice = devices[indexAvailable()];
187
1
    newDevice.reset(pti,*this);
188
1
189
1
    for (auto& delegate : delegates) {
190
1
      delegate.get().bleDatabaseDidCreate(newDevice);
191
1
    }
192
1
    // newDevice.payloadData(payloadData); // has to be AFTER create called
193
1
    device(newDevice,BLEDeviceAttribute::payloadData); // moved from BLEDevice.payloadData()
194
1
    return newDevice;
195
1
  }
196
197
17
  BLEDevice& device(const TargetIdentifier& targetIdentifier) override {
198
17
    auto results = matches([this,&targetIdentifier](const BLEDevice& d) {
199
10
      HTDBG(" Testing existing target identifier {} against new target identifier {}",(std::string)d.identifier(),(std::string)targetIdentifier);
200
10
      return d.identifier() == targetIdentifier;
201
10
    });
202
17
    if (results.size() != 0) {
203
2
      HTDBG("Device for target identifier {} already exists",(std::string)targetIdentifier);
204
2
      return results.front(); // TODO ensure we send back the latest, not just the first match
205
2
    }
206
15
    HTDBG("New target identified: {}",(std::string)targetIdentifier);
207
15
    BLEDevice& newDevice = devices[indexAvailable()];
208
15
    newDevice.reset(targetIdentifier,*this);
209
15
210
15
    for (auto& delegate : delegates) {
211
4
      delegate.get().bleDatabaseDidCreate(newDevice);
212
4
    }
213
15
    return newDevice;
214
15
  }
215
  
216
  // Introspection overrides
217
35
  std::size_t size() const override {
218
35
    std::size_t count = 0;
219
350
    for (auto& d : devices) {
220
350
      if (d.state() != BLEDeviceState::uninitialised) {
221
42
        ++count;
222
42
      }
223
350
    }
224
35
    return count;
225
35
  }
226
227
  std::vector<std::reference_wrapper<BLEDevice>> matches(
228
104
    const std::function<bool(const BLEDevice&)>& matcher) override {
229
104
    std::vector<std::reference_wrapper<BLEDevice>> results;
230
104
    // in the absence of copy_if in C++20... Just copies the pointers not the objects
231
1.14k
    for (auto iter = devices.begin();iter != devices.end();
++iter1.04k
) {
232
1.04k
      if (BLEDeviceState::uninitialised != iter->state() && 
matcher(*iter)124
) {
233
39
        results.push_back(std::reference_wrapper<BLEDevice>(*iter));
234
39
      }
235
1.04k
    }
236
104
    return results;
237
104
  }
238
239
  /// Cannot name a function delete in C++. remove is common.
240
4
  void remove(const TargetIdentifier& targetIdentifier) override {
241
4
    auto found = std::find_if(devices.begin(),devices.end(),
242
13
      [&targetIdentifier](BLEDevice& d) -> bool {
243
13
        return d.identifier() == targetIdentifier;
244
13
      }
245
4
    );
246
4
    if (found != devices.end()) {
247
3
      BLEDevice& toRemove = *found;
248
3
      remove(toRemove);
249
3
    }
250
4
  }
251
252
  // BLE Device Delegate overrides
253
20
  void device(const BLEDevice& device, BLEDeviceAttribute didUpdate) override {
254
20
    // Update any internal DB state as necessary (E.g. payload received and its a duplicate as mac has rotated)
255
20
    if (BLEDeviceAttribute::payloadData == didUpdate) {
256
8
      // check for all devices with this payload that are NOT THIS device
257
16
      auto oldMacsForSamePayload = matches([device](auto& devRef) {
258
16
        return devRef.identifier() != device.identifier() && 
259
16
               
devRef.payloadData().size() > 08
&&
devRef.payloadData() == device.payloadData()2
;
260
16
      });
261
8
      for (auto& oldMacDevice : oldMacsForSamePayload) {
262
2
        remove(oldMacDevice.get().identifier());
263
2
      }
264
8
    }
265
20
266
20
    // Now send update to delegates
267
20
    for (auto& delegate : delegates) {
268
6
      delegate.get().bleDatabaseDidUpdate(device, didUpdate); // TODO verify this is the right onward call
269
6
    }
270
20
  }
271
272
private:
273
  void assignAdvertData(BLEDevice& newDevice, std::vector<BLEAdvertSegment>&& toMove, 
274
    const std::vector<BLEAdvertManufacturerData>& manuData)
275
0
  {
276
0
    newDevice.advertData(std::move(toMove));
277
0
278
0
    // If it's an apple device, check to see if its on our ignore list
279
0
    auto appleDataSegments = BLEAdvertParser::extractAppleManufacturerSegments(manuData);
280
0
    if (0 != appleDataSegments.size()) {
281
0
      HTDBG("Found apple device");
282
0
      // HTDBG((std::string)mac);
283
0
      newDevice.operatingSystem(BLEDeviceOperatingSystem::ios);
284
0
      // TODO see if we should ignore this Apple device
285
0
      // TODO abstract these out eventually in to BLEDevice class
286
0
      bool ignore = false;
287
0
      /*
288
0
          "^10....04",
289
0
          "^10....14",
290
0
          "^0100000000000000000000000000000000",
291
0
          "^05","^07","^09",
292
0
          "^00","^1002","^06","^08","^03","^0C","^0D","^0F","^0E","^0B"
293
0
      */
294
0
      for (auto& segment : appleDataSegments) {
295
0
        HTDBG(segment.data.hexEncodedString());
296
0
        switch (segment.type) {
297
0
          case 0x00:
298
0
          case 0x05:
299
0
          case 0x07:
300
0
          case 0x09:
301
0
          case 0x06:
302
0
          case 0x08:
303
0
          case 0x03:
304
0
          case 0x0C:
305
0
          case 0x0D:
306
0
          case 0x0F:
307
0
          case 0x0E:
308
0
          case 0x0B:
309
0
            ignore = true;
310
0
            break;
311
0
          case 0x10:
312
0
            // check if second is 02
313
0
            if (segment.data.at(0) == std::byte(0x02)) {
314
0
              ignore = true;
315
0
            } else {
316
0
              // Check 3rd data bit for 14 or 04
317
0
              if (segment.data.at(2) == std::byte(0x04) || segment.data.at(2) == std::byte(0x14)) {
318
0
                ignore = true;
319
0
              }
320
0
            }
321
0
            break;
322
0
          default:
323
0
            break;
324
0
        }
325
0
      }
326
0
      if (ignore) {
327
0
        HTDBG(" - Ignoring Apple device due to Apple data filter");
328
0
        newDevice.ignore(true);
329
0
      } else {
330
0
        // Perform GATT service discovery to check for Herald service
331
0
        // NOTE: Happens from Connection request (handled by BLE Coordinator)
332
0
        HTDBG(" - Unknown apple device... Logging so we can discover services later");
333
0
      }
334
0
    } else {
335
0
      // Not a Herald android or any iOS - so inspect later (beacon or wearable)
336
0
      HTDBG("Unknown non Herald device - inspecting (might be a venue beacon or wearable)");
337
0
      // HTDBG((std::string)mac);
338
0
    }
339
0
  }
340
341
3
  void remove(BLEDevice& toRemove) {
342
3
    // Don't call delete/update if this device has never been initialised
343
3
    if (toRemove.state() == BLEDeviceState::uninitialised) {
344
0
      return;
345
0
    }
346
3
    toRemove.state(BLEDeviceState::uninitialised);
347
3
    // TODO validate all other device data is reset
348
3
    for (auto& delegate : delegates) {
349
2
      delegate.get().bleDatabaseDidDelete(toRemove);
350
2
    }
351
3
  }
352
353
16
  std::size_t indexAvailable() noexcept {
354
25
    for (std::size_t idx = 0;idx < devices.size();
++idx9
) {
355
25
      auto& device = devices[idx];
356
25
      if (BLEDeviceState::uninitialised == device.state()) {
357
16
        return idx;
358
16
      }
359
25
    }
360
16
    // If we've got here then there is no space available
361
16
    // Remove the oldest by lastUpdated
362
16
    auto comp = last_updated_descending();
363
0
    std::size_t oldestIndex = 0;
364
0
    for (std::size_t idx = 0;idx < devices.size();++idx) {
365
0
      if (!comp(devices[oldestIndex],devices[idx])) {
366
0
        // new oldest
367
0
        oldestIndex = idx;
368
0
      }
369
0
    }
370
0
    // Now notify we're deleting this device
371
0
    auto& oldest = devices[oldestIndex];
372
0
    remove(oldest);
373
0
    // Now re-use this reference
374
0
    return oldestIndex;
375
16
  }
376
377
  ContextT& ctx;
378
  std::vector<std::reference_wrapper<BLEDatabaseDelegate>> delegates;
379
  std::array<BLEDevice,MaxDevices> devices; // bool = in-use (not 'removed' from DB)
380
381
  HLOGGER(ContextT);
382
};
383
384
}
385
}
386
387
#endif